#include <bsl_err.h>
#include <bsl_sal.h>
#include <crypt_algid.h>
#include <crypt_eal_md.h>
#include <crypt_eal_provider.h>
#include <crypt_errno.h>
#include <crypt_sha1.h>

#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <cmocka.h>

#include "helper.h"

#define GROUP_TEST_MAX 100

typedef struct {
    char *dataA;
    char *dataB;
    char *dataC;
    char *hash;
} CRYPT_SHA1_MultiData;

typedef struct {
    char *in;
    char *out;
} CRYPT_SHA1_InOut;

static CRYPT_EAL_LibCtx *CreateLoadLibCtx()
{
    CRYPT_EAL_LibCtx *libctx = CRYPT_EAL_LibCtxNew();
    assert_non_null(libctx);
    assert_int_equal(CRYPT_EAL_ProviderSetLoadPath(libctx, "../../build"), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_ProviderLoad(libctx, BSL_SAL_LIB_FMT_LIBSO, "kae_provider", NULL, NULL), CRYPT_SUCCESS);
    return libctx;
}

static void SDV_KAE_SHA1_PROVIDER_LOAD(void **state)
{
    (void)state;
    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_EAL_SHA1_API_TC001(void **state)
{
    (void)state;
    uint8_t input[100]; // Any length, for example, 100 bytes.
    uint32_t inLen = sizeof(input);
    uint8_t out[20]; // SHA1 digest length is 20.
    uint32_t outLen = sizeof(out);
    uint32_t badOutLen = outLen - 1;
    uint32_t longOutLen = outLen + 1;

    assert_int_equal(CRYPT_EAL_MdGetDigestSize(CRYPT_MD_SHA1), outLen);
    CRYPT_EAL_MdCTX *ctx = NULL;

    assert_int_equal(CRYPT_EAL_MdDeinit(ctx), CRYPT_NULL_INPUT);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");
    assert_non_null(ctx);

    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &outLen), CRYPT_EAL_ERR_STATE);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, inLen), CRYPT_EAL_ERR_STATE);
    assert_int_equal(CRYPT_EAL_MdInit(ctx), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdUpdate(NULL, input, inLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, NULL, inLen), CRYPT_NULL_INPUT);
    // Hash counting can be performed on empty strings.
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, 0), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, NULL, 0), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, inLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdFinal(ctx, NULL, &outLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(NULL, out, &outLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, NULL), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &badOutLen), CRYPT_EAL_BUFF_LEN_NOT_ENOUGH);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &outLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdInit(ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, input, inLen), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &longOutLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdGetId(ctx), CRYPT_MD_SHA1);
    assert_int_equal(CRYPT_EAL_MdDeinit(ctx), CRYPT_SUCCESS);

    CRYPT_EAL_MdFreeCtx(ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static const char SDV_KAE_SHA1_FUNC_TC001_HEX[] = "da39a3ee5e6b4b0d3255bfef95601890afd80709";

static void SDV_KAE_SHA1_FUNC_TC001(void **state)
{
    const char *expect = *((const char **)state);
    Hex *hex = NewHex();
    assert_int_equal(ConvertHex(expect, hex), 0);

    uint8_t output[CRYPT_SHA1_DIGESTSIZE];
    uint32_t outLen = CRYPT_SHA1_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *sha1Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");
    assert_non_null(sha1Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(sha1Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(sha1Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SHA1_DIGESTSIZE);

    assert_int_equal(outLen, hex->len);
    assert_memory_equal(output, hex->x, outLen);

    FreeHex(hex);
    CRYPT_EAL_MdFreeCtx(sha1Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

#define SDV_KAE_SHA1_FUNC_TC002_N 5

static const char *SDV_KAE_SHA1_FUNC_TC002_HEX[SDV_KAE_SHA1_FUNC_TC002_N][2] = {
    {"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
    {"487351c8a5f440e4d03386483d5fe7bb669d41adcbfdb7", "dbc1cb575ce6aeb9dc4ebf0f843ba8aeb1451e89"},
    {
        "f2c76ef617fa2bfc8a4d6bcbb15fe88436fdc2165d3074629579079d4d5b86f5081ab177b4c3f530376c9c924cbd421a8daf8830d0940c4fb7589865830699",
        "9f3ea255f6af95c5454e55d7354cabb45352ea0b",
    },
    {
        "0321736beba578e90abc1a90aa56157d871618f6de0d764cc8c91e06c68ecd3b9de3824064503384db67beb7fe012232dacaef93a000fba7",
        "aef843b86916c16f66c84d83a6005d23fd005c9e",
    },
    {
        "892af4c05368aa9242acedd87d0fc68de483ab59520aea621f264b65ea90f005952c8163903d86ee5bd6147d4691ac9b7c8260213f6e370b7539d384649e5143ba23711ad04bf7cc2f0d512054857933b0ea1d12f3c0fe888a4e96356653fde000f50d0f9afac5d4c73aebe92d54f5ff8aa12a54f5660584674edaa17917bb856f8b9d6776b2b7ad2a462b015b67e8a71190cf0ecdca15a5121fe8ef245255da10cd694decdb96006017599066251ad34d9f54690452f59395ab0848f06c9186eaa3b8e785dd2a747297bdbdd4f5532a47b7008c21686ff7f8d881d464cd383205f6d45dc8203bb267ac9eb12f415a5406be1c9fac73497941909dba08dd12856aac03d83e0d916147404694fe70f8fa929ef0cc2edb4cc07abaa2236405e62820af8e806d0af32a1b3afb8dcaeaf5c4f43dc4392e074075aa3ed93601ab7ec22fe5bd7cdf802bb5ea8206c41a1619593385e00e3461ed3fda048a1c6639a0fca038d7f51cd8ffa9bc00af62765e2b62575c8b74c8501ac711f3fdfc1b15157e7a8f2612aa7838af999c3d8f6629f58669ac0f93733c91b557f579ffa9a9a4efc5d1f0fc13ca9e6e8a3efa7273e03d6e705cb292bc8d18b0b4f1484d975b17f88ae87edadf34f88f96ce2c3424e9ccc17454bd992cac786031d0b00d6d953540d0bb18d5942010b9c6341cfc02ad6a287e7c78d249ff796ed578fa68b4bec5709f320515bcf5ac95215812f39494de4b94bc2a639eefe282a9d26d85f33d902fff358fc1de1b95caaf2255416207f2d1c1fc1c74b0e57d43b3c6538db27c5e26f9acfc0183fa9301787b2f0df46c6c630a24972e0947105afd3df2a779e2f6fc947f95ff32fa6de28549e67fd32c15a8791ce1b8307e646e8f1d94fcd1d7225ad997a2e07383ed14dd76c3c186b0b54915cc",
        "20a3a677c117c61ed3bb19e2ac77f69987896d0b",
    }};

static void SDV_KAE_SHA1_FUNC_TC002(void **state)
{
    uint8_t output[CRYPT_SHA1_DIGESTSIZE];
    uint32_t outLen = CRYPT_SHA1_DIGESTSIZE;

    const char **test = (const char **)*state;
    const char *in = test[0];
    const char *ou = test[1];

    Hex *msg = NewHex();
    Hex *out = NewHex();

    assert_int_equal(ConvertHex(in, msg), 0);
    assert_int_equal(ConvertHex(ou, out), 0);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *sha1Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");
    assert_non_null(sha1Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(sha1Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(sha1Ctx, msg->x, msg->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(sha1Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SHA1_DIGESTSIZE);

    assert_int_equal(outLen, out->len);
    assert_memory_equal(output, out->x, outLen);

    FreeHex(msg);
    FreeHex(out);

    CRYPT_EAL_MdFreeCtx(sha1Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_SHA1_FUNC_TC003(void **state)
{
    (void)state;

    uint8_t input[5050];
    uint32_t inLenTotal = 0;
    uint32_t inLenBase;
    uint8_t outA[CRYPT_SHA1_DIGESTSIZE];
    uint8_t outB[CRYPT_SHA1_DIGESTSIZE];
    uint32_t outLen = CRYPT_SHA1_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *ctxA = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");
    CRYPT_EAL_MdCTX *ctxB = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");

    assert_non_null(ctxA);
    assert_non_null(ctxB);

    assert_int_equal(CRYPT_EAL_MdInit(ctxA), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdInit(ctxB), CRYPT_SUCCESS);

    for (inLenBase = 1; inLenBase <= 100; inLenBase++) {
        assert_int_equal(CRYPT_EAL_MdUpdate(ctxA, input + inLenTotal, inLenBase), CRYPT_SUCCESS);
        inLenTotal += inLenBase;
    }

    assert_int_equal(CRYPT_EAL_MdFinal(ctxA, outA, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SHA1_DIGESTSIZE);

    assert_int_equal(CRYPT_EAL_MdUpdate(ctxB, input, inLenTotal), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctxB, outB, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_SHA1_DIGESTSIZE);

    assert_memory_equal(outA, outB, outLen);

    CRYPT_EAL_MdFreeCtx(ctxA);
    CRYPT_EAL_MdFreeCtx(ctxB);
    CRYPT_EAL_LibCtxFree(libctx);
}

static CRYPT_SHA1_MultiData SDV_KAE_SHA1_FUNC_TC004_VEC[] = {
    {
        "f2c76ef6",
        "17fa2bfc8a4d6bcbb15fe88436fdc2165d3074629579079d4d5b86f5081ab177",
        "b4c3f530376c9c924cbd421a8daf8830d0940c4fb7589865830699",
        "9f3ea255f6af95c5454e55d7354cabb45352ea0b",
    },
    {
        "892af4c053",
        "68aa9242acedd87d0fc68de483ab59520aea621f264b65ea90f005952c8163903d86ee5bd6147d4691ac9b7c8260213f6e370b7539d384649e5143ba23711ad04bf7cc2f0d512054857933b0ea1d12f3c0fe888a4e96356653fde000f50d0f9afac5d4c73aebe92d54f5ff8aa12a54f5660584674edaa17917bb856f8b9d6776b2b7ad2a462b015b67e8a71190cf0ecdca15a5121fe8ef245255da10cd694decdb96006017599066251ad34d9f54690452f59395ab0848f06c9186eaa3b8e785dd2a747297bdbdd4f5532a47b7008c21686ff7f8d881d464cd383205f6d45dc8203bb267ac9eb12f415a5406be1c9fac73497941909dba08dd12856aac03d83e0d916147404694fe70f8fa929ef0cc2edb4cc07abaa2236405e62820af8e806d0af32a1b3afb8dcaeaf5c4f43dc4392e074075aa3ed93601ab7ec22fe5bd7cdf802bb5ea8206c41a1619593385e00e3461ed3fda048a1c6639a0fca038d7f51cd8ffa9bc00af62765e2b62575c8b74c8501ac711f3fdfc1b15157e7a8f2612aa7838af999c3d8f6629f58669ac0f93733c91b557f579ffa9a9a4efc5d1f0fc13ca9e6e8a3efa7273e03d6e705cb292bc8d18b0b4f1484d975b17f88ae87edadf34f88f96ce2c3424e9ccc17454bd992cac786031d0b00d6d953540d0bb18d5942010b9c6341cfc02ad6a287e7c78d249ff796ed578fa68b4bec5709f320515bcf5ac95215812f39494de4b94bc2a639eefe282a9d26d85f33d902fff358fc1de1b95caaf2255416207f2d1c1fc1c74b0e57d43b3c6538db27c5e26f9acfc0183fa9301787b2f0df46c6c630a24972e0947105afd3df2a779e2f6fc947f95ff32fa6de28549e67fd32c15a8791ce1b8307e646e8f1d94fcd1d7225ad997a2e07383ed14dd76c3c186b0b5",
        "4915cc",
        "20a3a677c117c61ed3bb19e2ac77f69987896d0b",
    }};

static void SDV_KAE_SHA1_FUNC_TC004(void **state)
{
    CRYPT_SHA1_MultiData *test = (CRYPT_SHA1_MultiData *)*state;

    Hex *data1 = NewHex();
    Hex *data2 = NewHex();
    Hex *data3 = NewHex();
    Hex *hash = NewHex();

    assert_int_equal(ConvertHex(test->dataA, data1), 0);
    assert_int_equal(ConvertHex(test->dataB, data2), 0);
    assert_int_equal(ConvertHex(test->dataC, data3), 0);
    assert_int_equal(ConvertHex(test->hash, hash), 0);

    uint8_t out[32]; // 32 is sha1 hash size
    uint32_t outLen = sizeof(out);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");
    assert_non_null(ctx);

    assert_int_equal(CRYPT_EAL_MdInit(ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, data1->x, data1->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, data2->x, data2->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(ctx, data3->x, data3->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctx, out, &outLen), CRYPT_SUCCESS);

    assert_int_equal(outLen, 20);
    assert_int_equal(memcmp(out, hash->x, hash->len), 0);

    FreeHex(data1);
    FreeHex(data2);
    FreeHex(data3);
    FreeHex(hash);

    CRYPT_EAL_MdFreeCtx(ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static CRYPT_SHA1_InOut SDV_KAE_SHA1_DUP_TC001_VEC[] = {
    {"487351c8a5f440e4d03386483d5fe7bb669d41adcbfdb7", "dbc1cb575ce6aeb9dc4ebf0f843ba8aeb1451e89"},
};

static void SDV_KAE_SHA1_DUP_TC001(void **state)
{
    CRYPT_SHA1_InOut *test = (CRYPT_SHA1_InOut *)*state;

    uint8_t output[CRYPT_SHA1_DIGESTSIZE];
    uint32_t outLen = CRYPT_SHA1_DIGESTSIZE;

    Hex *msg = NewHex();
    Hex *out = NewHex();

    assert_int_equal(ConvertHex(test->in, msg), 0);
    assert_int_equal(ConvertHex(test->out, out), 0);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *sha1Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_SHA1, "provider=kaev1");
    assert_non_null(sha1Ctx);

    CRYPT_EAL_MdCTX *dupCtx = NULL;
    dupCtx = CRYPT_EAL_MdDupCtx(NULL);
    assert_null(dupCtx);

    dupCtx = CRYPT_EAL_MdDupCtx(sha1Ctx);
    assert_non_null(dupCtx);
    assert_int_equal(CRYPT_EAL_MdInit(dupCtx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(dupCtx, msg->x, msg->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(dupCtx, output, &outLen), CRYPT_SUCCESS);

    assert_int_equal(outLen, out->len);
    assert_memory_equal(output, out->x, outLen);

    FreeHex(msg);
    FreeHex(out);

    CRYPT_EAL_MdFreeCtx(sha1Ctx);
    CRYPT_EAL_MdFreeCtx(dupCtx);
    CRYPT_EAL_LibCtxFree(libctx);
}

int main(void)
{
    struct CMUnitTest tests[GROUP_TEST_MAX] = {NULL};
    int testCnt = 0;

    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_SHA1_PROVIDER_LOAD);
    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_EAL_SHA1_API_TC001);
    tests[testCnt++] =
        (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SHA1_FUNC_TC001, (void **)&SDV_KAE_SHA1_FUNC_TC001_HEX);
    for (size_t i = 0; i < SDV_KAE_SHA1_FUNC_TC002_N; i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SHA1_FUNC_TC002, &SDV_KAE_SHA1_FUNC_TC002_HEX[i]);
    }
    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_SHA1_FUNC_TC003);
    for (size_t i = 0; i < sizeof(SDV_KAE_SHA1_FUNC_TC004_VEC) / sizeof(SDV_KAE_SHA1_FUNC_TC004_VEC[0]); i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SHA1_FUNC_TC004, &SDV_KAE_SHA1_FUNC_TC004_VEC[i]);
    }
    for (size_t i = 0; i < sizeof(SDV_KAE_SHA1_DUP_TC001_VEC) / sizeof(SDV_KAE_SHA1_DUP_TC001_VEC[0]); i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_SHA1_DUP_TC001, &SDV_KAE_SHA1_DUP_TC001_VEC[i]);
    }
    cmocka_run_group_tests(tests, NULL, NULL);
    return 0;
}